package com.cv.platform.service.search;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.cv.framework.common.pojo.PageResult;
import com.cv.framework.common.utils.object.BeanUtils;
import com.cv.infra.api.file.FileApi;
import com.cv.platform.controller.platform.label.vo.record.CvVideoLabelRespVO;
import com.cv.platform.controller.platform.search.vo.CvVideoSearchReqVO;
import com.cv.platform.controller.platform.video.vo.CvVideoItemRespVO;
import com.cv.platform.dal.dataobject.search.CvSearchRecordDO;
import com.cv.platform.dal.dataobject.video.CvVideoDO;
import com.cv.platform.dal.es.index.video.CvVideoEntity;
import com.cv.platform.dal.es.repository.video.CvVideoEntityRepository;
import com.cv.platform.dal.mapper.search.CvSearchRecordMapper;
import com.cv.platform.enums.SortedTypeEnum;
import com.cv.platform.service.label.CvVideoLabelService;
import com.cv.platform.service.video.CvVideoService;
import com.cv.system.api.dict.DictDataApi;
import com.cv.system.api.dict.dto.DictDataRespDTO;
import com.cv.system.api.user.UserApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static com.cv.platform.dal.redis.RedisKeyConstants.DAILY_SEARCH_RECORD;

/**
 * 视频搜索业务层接口实现类
 *
 * @author Charles_XDXD
 */
@Service
@Slf4j
public class CvVideoSearchServiceImpl implements CvVideoSearchService {

    @Resource
    private CvSearchRecordMapper cvSearchRecordMapper;

    @Resource
    private CvVideoEntityRepository cvVideoEntityRepository;

    @Resource
    private CvVideoService cvVideoService;

    @Resource
    private CvVideoLabelService cvVideoLabelService;

    @Resource
    private RedisTemplate redisTemplate;

    @DubboReference
    private FileApi fileApi;

    @DubboReference
    private UserApi userApi;

    @DubboReference
    private DictDataApi dictDataApi;

    @Override
    public PageResult<CvVideoItemRespVO> searchVideo(CvVideoSearchReqVO reqVO) {
        if (StrUtil.isNotEmpty(reqVO.getKeyword())) {
            // 将当日的搜索记录存储到缓存中
            LocalDate today = LocalDate.now();
            String key = String.format(DAILY_SEARCH_RECORD, today);
            redisTemplate.opsForZSet().incrementScore(key, reqVO.getKeyword(), 1);
        }

        Sort sort = null;
        if (reqVO.getSorted().equals(SortedTypeEnum.NEW.getCode())) {
            sort = Sort.by(Sort.Direction.DESC, "publishTime");
        } else if (reqVO.getSorted().equals(SortedTypeEnum.VIEW_COUNT.getCode())) {
            sort = Sort.by(Sort.Direction.DESC, "viewCount");
        }

        // ES 查询是从 0 开始分页
        PageRequest pageRequest;
        if (ObjUtil.isEmpty(sort)) {
            pageRequest = PageRequest.of(reqVO.getPageNo() - 1, reqVO.getPageSize());
        } else {
            pageRequest = PageRequest.of(reqVO.getPageNo() - 1, reqVO.getPageSize(), sort);
        }

        Page<CvVideoEntity> searchResult = cvVideoEntityRepository.searchVideo(reqVO.getKeyword(), pageRequest);
        List<CvVideoEntity> searchResultList = searchResult.get().toList();
        if (CollUtil.isEmpty(searchResultList)) {
            return PageResult.empty();
        }
        PageResult<CvVideoItemRespVO> pageResult = new PageResult<>();
        List<CvVideoItemRespVO> pageResultList = new ArrayList<>();
        pageResult.setTotal(searchResult.getTotalElements());

        searchResultList.forEach(item -> {
            CvVideoItemRespVO videoItem = BeanUtils.toBean(item, CvVideoItemRespVO.class);
            videoItem.setNickname(userApi.getNicknameById(item.getUserId()));
            videoItem.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
            pageResultList.add(videoItem);
        });
        pageResult.setList(pageResultList);

        return pageResult;
    }

    @Override
    public List<CvVideoEntity> getAllVideoOrderByViewCount() {
        List<CvVideoEntity> videoList = StreamSupport.stream(cvVideoEntityRepository.findAll().spliterator(), false)
                .collect(Collectors.toList());

        if (CollUtil.isEmpty(videoList)) {
            return Collections.emptyList();
        }

        videoList.forEach(item -> {
            item.setType(getVideoType(item.getType()));
        });

        videoList.sort(Comparator.comparing(CvVideoEntity::getViewCount, Comparator.nullsFirst(Comparator.reverseOrder())));
        return videoList;
    }

    @Override
    @Async
    public void syncData() {
        log.info("[CvVideoSearchImpl.syncData]: 开始同步视频数据到 ES 中");
        // 清空 ES 中的数据
        cvVideoEntityRepository.deleteAll();

        List<CvVideoItemRespVO> allAllowedVideo = cvVideoService.getAllAllowedVideo();
        if (CollUtil.isNotEmpty(allAllowedVideo)) {
            allAllowedVideo.forEach(video -> {
                List<CvVideoLabelRespVO> labelRespVOList = cvVideoLabelService.getAllLabelByVideoId(video.getId());
                String[] labels = labelRespVOList.stream()
                        .map(CvVideoLabelRespVO::getLabelName)
                        .toArray(String[]::new);
                cvVideoEntityRepository.save(CvVideoEntity.builder()
                        .id(video.getId())
                        .userId(video.getUserId())
                        .nickname(video.getNickname())
                        .title(video.getTitle())
                        .type(getVideoTypeByDictValue(video.getType()))
                        .labels(List.of(labels.length > 0 ? labels : new String[]{}))
                        .viewCount(video.getViewCount())
                        .thumbnail(video.getThumbnail())
                        .publishTime(video.getPublishTime())
                        .build()
                );
            });
        }

        log.info("[CvVideoSearchImpl.syncData]: 同步视频数据到 ES 中完成");
    }

    @Override
    public List<String> getHotSearch() {
        LocalDate today = LocalDate.now();
        String key = String.format(DAILY_SEARCH_RECORD, today);
        Set<String> set = redisTemplate.opsForZSet().reverseRange(key, 0, 4);

        if (CollUtil.isEmpty(set)) {
            return Collections.emptyList();
        }
        return set.stream().toList();
    }

    @Override
    public void syncSearchRecord() {
        log.info("[CvVideoSearchImpl.syncSearchRecord]: 开始同步搜索记录到数据库中");
        LocalDate yesterday = LocalDate.now().minusDays(1);
        String key = String.format(DAILY_SEARCH_RECORD, yesterday);

        // 从 Redis 中获取昨天的热搜记录
        Set<String> keywords = redisTemplate.opsForZSet().range(key, 0, -1);
        if (keywords == null || keywords.isEmpty()) {
            log.info("[CvVideoSearchImpl.syncSearchRecord]: 昨天没有搜索记录");
            return;
        }

        // 获取每个关键词的搜索次数
        List<CvSearchRecordDO> searchRecords = keywords.stream()
                .map(keyword -> {
                    Double score = redisTemplate.opsForZSet().score(key, keyword);
                    return CvSearchRecordDO.builder()
                            .keyword(keyword)
                            .searchCount(score != null ? score.intValue() : 0)
                            .build();
                })
                .collect(Collectors.toList());

        cvSearchRecordMapper.insertOrUpdateBatch(searchRecords);
        log.info("[CvVideoSearchImpl.syncSearchRecord]: 同步搜索记录到数据库中完成");
    }

    @Override
    @Async
    public void insertOrUpdate(CvVideoDO cvVideoDO) {
        if (ObjUtil.isNotEmpty(cvVideoDO) && ObjUtil.isNotEmpty(cvVideoDO.getId())) {
            log.info("[CvVideoSearchImpl.insertOrUpdate]: 开始同步视频数据到 ES 中");
            List<CvVideoLabelRespVO> labelList = cvVideoLabelService.getAllLabelByVideoId(cvVideoDO.getId());
            String[] labels = labelList.stream()
                    .map(CvVideoLabelRespVO::getLabelName)
                    .toArray(String[]::new);
            cvVideoEntityRepository.save(CvVideoEntity.builder()
                    .id(cvVideoDO.getId())
                    .userId(cvVideoDO.getUserId())
                    .nickname(userApi.getNicknameById(cvVideoDO.getUserId()))
                    .title(cvVideoDO.getTitle())
                    .type(getVideoTypeByDictValue(cvVideoDO.getType()))
                    .labels(List.of(labels.length > 0 ? labels : new String[]{}))
                    .viewCount(cvVideoDO.getViewCount())
                    .thumbnail(cvVideoDO.getThumbnail())
                    .publishTime(cvVideoDO.getCreateTime())
                    .build());
            log.info("[CvVideoSearchImpl.insertOrUpdate]: 同步视频数据到 ES 中完成");
        }
    }

    @Override
    public void delete(Long videoId) {
        if (ObjUtil.isNotEmpty(videoId)) {
            log.info("[CvVideoSearchImpl.delete]: 开始删除视频数据到 ES 中");
            cvVideoEntityRepository.deleteById(videoId);
            log.info("[CvVideoSearchImpl.delete]: 删除视频数据到 ES 中完成");
        }
    }

    /**
     * 将视频类型转为它的字典值
     */
    private String getVideoType(String label) {
        return Optional.ofNullable(dictDataApi.parseDictData("cv_video_types", label))
                .orElse(new DictDataRespDTO().setValue(StrUtil.EMPTY)).getValue();
    }

    /**
     * 将字典值转为视频类型
     */
    private String getVideoTypeByDictValue(String dictValue) {
        return Optional.ofNullable(dictDataApi.getDictData("cv_video_types", dictValue))
                .orElse(new DictDataRespDTO().setLabel(StrUtil.EMPTY)).getLabel();
    }

}
